package org.kth.dks.dks_marshal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.kth.dks.DKSObject;
import org.kth.dks.dks_comm.DKSNetAddress;
import org.kth.dks.dks_comm.DKSRef;
import org.kth.dks.util.Future;
import org.kth.dks.util.ReusableFuture;

/**
 * <p>Title: DKS</p>
 *
 * <p>Description: DKS Middleware</p>
 *
 * <p>Copyright: Copyright (c) 2005</p>
 *
 * <p>Company: KTH-IMIT/SICS</p>
 *
 * @author Ali Ghodsi (aligh@kth.se)
 * @version 1.0
 */

public class MarshalXML extends MarshalInterface {
  private static Logger log = Logger.getLogger(MarshalXML.class);
  protected int pCount = 0;
  protected XMLMessage xmsg = null;
  protected XMLElement ele;
  protected int eleCnt = 0;

  private static final String BASEPACKAGE = "org.kth.dks.";

  private static Future xmlPlaceholder = new ReusableFuture(); // INVARIANT: never concurrently accessed
  private static XMLSAXParser saxParser = new XMLSAXParser( xmlPlaceholder );
//  private final Map messageMap;
//  private final DKSMarshal dksMarshal;

  public MarshalXML(DKSMessage m) {
    super(m);

//    messageMap = msgMap;
//    dksMarshal = dm;

  }

  public byte[] flatten() {
    pCount = 0;
    eleCnt = 0;
    xmsg = new XMLMessage();
    ele = new XMLElement(msg.getName(), null);
    xmsg.addElement(ele);

    try {
      if (msg.getSendRef() != null) msg.marshaler.addDKSRef(msg.getSendRef(), "SrcRef");
      if (msg.getRecvRef() != null) msg.marshaler.addDKSRef(msg.getRecvRef(), "DstRef");
      msg.marshal();
    } catch (IOException ex) {
      log.error( "Error marshaling:\n"+ex);
    }
    return marshalXMLToMessage(xmsg);
  }

  public void unflatten(DKSMessage msg, XMLMessage xml) {
    pCount = 0;
    eleCnt = 0;
    xmsg = xml;
    ele = xml.getElementAt(0);

    try {
      msg.setSendRef(msg.marshaler.remDKSRef("SrcRef"));
      msg.setRecvRef(msg.marshaler.remDKSRef("DstRef"));
      msg.unmarshal();
    } catch (IOException ex) {
      log.error( "Error unmarshaling:\n"+ex);
    }

    xmsg = null; // gc
    ele  = null; // gc
  }


  public static DKSMessage unflatten(byte[] raw) {
    XMLMessage xmlMsg = byteToXML(raw);

    DKSMessage dksMsg = xmlToDKSMessage(xmlMsg);

    if ( null == dksMsg ) {
      log.error( "DKSMarshal -- Got an unknown message " +
                       xmlMsg.getElementAt(0).getName());
      return null;
    }

    ((MarshalXML)dksMsg.marshaler).unflatten(dksMsg, xmlMsg);

//    DKSPrint.println(DKSPrintTypes.MSGS, "receive remote("+dksMsg.getRecvRef().getID()+"<-" +
//                     dksMsg.getSendRef().getID()+"): "+dksMsg.getName());
    return dksMsg;
  }


  public static  byte[] marshalXMLToMessage( XMLMessage xmlmsg ) {
    String xml = XMLSAXParser.make( xmlmsg );//todo:String might be too small!!!
    return xml.getBytes();
  }

  public synchronized static XMLMessage byteToXML( byte[] input) {
    saxParser.parse(input); // launches a separate thread which will parse the document
    XMLMessage result = null;
    try {
      result = (XMLMessage) xmlPlaceholder.get(); // blocking! gets the resulting XML object from the buffer
    } catch (Exception ex) {
      log.error(
                     "Interrupted while waiting for SAX parser to parse document ("+ex.getMessage()+")\n");
    }
    return result;
  }

  public static DKSMessage xmlToDKSMessage(XMLMessage xmlMsg) {
    if( xmlMsg == null )
      return null;

    XMLElement xmlElement = xmlMsg.getElementAt(0);
    if( xmlElement == null )
      return null;

    if (DKSMessage.messageMap.get(xmlElement.getName())!=null) {
      try {

        Class messageClass = (Class) DKSMessage.messageMap.get(xmlElement.getName());

        DKSMessage msg = ((DKSMessage) messageClass.newInstance());
        return msg;

      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
    return null;
  }

  // ############# STANDARD HANDLERS ################

  public void addLong(long val) {
    addLong(val, String.valueOf(pCount));
  }

  public void addLong(long val, String na) {
    ele.addAttribute(new XMLAttribute(na, String.valueOf(val)));
    pCount++;
  }

  public void addInt(int val) {
    addInt(val, String.valueOf(pCount));
  }

 public void addInt(int val, String na) {
    ele.addAttribute(new XMLAttribute(na, String.valueOf(val)));
    pCount++;
  }

  public void addString(String val) {
    addString(val, String.valueOf(pCount));
  }

  public void addString(String val, String na) {
    ele.addAttribute(new XMLAttribute(na, val));
    pCount++;
  }

  public void addBool(boolean val) {
    addBool(val, String.valueOf(pCount));
  }

  public void addBool(boolean val, String na) {
    ele.addAttribute(new XMLAttribute(na, Boolean.toString(val)));
    pCount++;
  }

  public void addDKSObject(DKSObject val) {
    addDKSObject(val, String.valueOf(pCount));
  }

  public void addDKSObject(DKSObject val, String na) {
    ele.addAttribute(new XMLAttribute(na, val.toString()));
    pCount++;
  }

  public void addDKSRef(DKSRef val) {
    addDKSRef(val, String.valueOf(pCount));
  }

  public void addDKSRef(DKSRef val, String na) {
    ele.addAttribute(new XMLAttribute(na, val.getDKSURL()));
    pCount++;
  }

  public void addDKSNetAddress(DKSNetAddress val) {
    addDKSNetAddress(val, String.valueOf(pCount));
  }

  public void addDKSNetAddress(DKSNetAddress val, String na) {
    ele.addAttribute(new XMLAttribute(na, val.getDKSNetURL()));
    pCount++;
  }

  public void addDKSRefArray(DKSRef[] val) {
    addDKSRefArray(val, String.valueOf(pCount));
  }

  public void addDKSRefArray(DKSRef[] val, String na) {
    XMLElement ele = new XMLElement(na, "");
    for (int i = 0; i < val.length; i++) {
      ele.addAttribute(new XMLAttribute("DKSREF" + i, val[i].getDKSURL()));
    }
    xmsg.addElement(ele);
    pCount++;
  }

  public final void addByteArray(byte[] m) throws IOException {
    addByteArray(m, "");
  }

  public final void addByteArray(byte[] m, String na) throws IOException {
    DKSObject o = new DKSObject(m);
    addString(o.toString(), na);
  }


  // ######################## REMOVE OPERATIONS ##########################

  public String remString() {
    return remString(String.valueOf(pCount));
  }

  public String remString(String na) {
    do {
      for (Iterator i = ele.getAttributes().iterator(); i.hasNext(); ) {

        XMLAttribute attrs = (XMLAttribute) i.next();
        if (attrs.getName().equals(na)) {
          pCount++;
          i.remove();
          return attrs.getValue();
        }
      }
      eleCnt++;
      ele = xmsg.getElementAt(eleCnt);
    }
    while (eleCnt < xmsg.size());
    eleCnt = 0;
    ele = xmsg.getElementAt(eleCnt);

    return null;
  }

  public long remLong() {
    return remLong(String.valueOf(pCount));
  }

  public long remLong(String na) {
    return Long.parseLong(remString(na));
  }

  public int remInt() {
    return remInt(String.valueOf(pCount));
  }

  public int remInt(String na) {
    return Integer.parseInt(remString(na));
  }

  public boolean remBool() {
    return remBool(String.valueOf(pCount));
  }

  public boolean remBool(String na) {
    return Boolean.valueOf(remString(na)).booleanValue();
  }

  public DKSObject remDKSObject() {
    return remDKSObject(String.valueOf(pCount));
  }

  public DKSObject remDKSObject(String na) {
    String s = remString(na);
    return na!=null ? new DKSObject( s  ) : null;
  }

  public DKSRef remDKSRef() {
    return remDKSRef(String.valueOf(pCount));
  }

  public DKSRef remDKSRef(String na) {
    try {
      String s = remString(na);

      return s==null ? null : DKSRef.valueOf(s);
    }
    catch (Exception ex) {
      ex.printStackTrace();
      return null;
    }
  }

  public DKSNetAddress remDKSNetAddress() {
    return remDKSNetAddress(String.valueOf(pCount));
  }

  public DKSNetAddress remDKSNetAddress(String na) {
    try {
      String s = remString(na);

      return s==null ? null : new DKSNetAddress(s);
    }
    catch (Exception ex) {
      ex.printStackTrace();
      return null;
    }
  }

  public List remDKSRefArray() {
    return remDKSRefArray(String.valueOf(pCount));
  }

  public List remDKSRefArray(String na) {
    List l = new ArrayList();
    for (Iterator i = xmsg.getElements().iterator(); i.hasNext(); ) {
      XMLElement e = (XMLElement) i.next();
      if (e.getName().equals(na)) {
        for (Iterator j = e.getAttributes().iterator(); j.hasNext(); ) {
          XMLAttribute a = (XMLAttribute) j.next();
          try {
            l.add(DKSRef.valueOf(a.getValue()));
          }
          catch (Exception ex) {
            ex.printStackTrace();
          }
        }
        pCount++;
        return l;
      }
    }
    return null;
  }

  public final  byte[] remByteArray() throws IOException {
    return remByteArray("");
  }

  public final byte[] remByteArray(String na) throws IOException {
    DKSObject o = new DKSObject(remString(na));
    return o.getData();
  }

}
